package dao.hibernate;

import dao.common.ProductManager;
import models.entities.PaginatedList;
import models.entities.SiteMapPath;
import models.entities.SitePath;
import models.exceptions.CreateEntityException;
import models.shop.*;
import models.shop.path.ProductFilter;
import play.mvc.results.NotFound;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;

public class ProductManagerHB implements ProductManager {
    public void save(Product product, int categoryId) throws CreateEntityException {
        if (categoryId == 0) {
            throw new CreateEntityException("Category id must be set");
        }
        Category category = new Category();
        category.id = categoryId;
        product.category = category;
        save(product);
    }

    public Product getById(int id) {
        return getById(id, false);
    }

    public Product getById(int id, boolean fetchCategory) {
        String productQuery = getProductQuery(fetchCategory);
        Product product = Product.find(productQuery, id).first();
        return product;
    }

    private String getProductQuery(boolean fetchCategory) {
        if (!fetchCategory) {
            return "select " +
                    "new Product(p.id, " +
                    "   p.title, " +
                    "   p.price, " +
                    "   p.shortDescription, " +
                    "   p.description, " +
                    "   p.images.size as imagesCount," +
                    "   p.archived)" +
                    " from Product p where p.id = ?";
        } else {
            return "select " +
                    "new Product(p.id, " +
                    "   p.title, " +
                    "   p.price, " +
                    "   p.shortDescription, " +
                    "   p.description, " +
                    "   p.images.size as imagesCount," +
                    "   c," +
                    "   p.archived)" +
                    " from Product p, Category c where p.category.id = c.id and p.id = ?";
        }
    }

    public void clearCache() {
        Product.em().clear();
    }

    public void save(Product product) {

        product.save();
    }

    public void save(Product product, String categoryTitle) {
        Category category = Category.find("title like ?", categoryTitle).first();
        product.category = category;
        save(product);
    }

    public List<Product> list(int categoryId) {
        return list(categoryId, 1, 20);
    }

    public List<Product> list(int categoryId, int pageNumber, int pageSize) {
        return Product.find("byCategoryId", categoryId).fetch(pageNumber, pageSize);
    }

    public List<Product> listTopProducts(int count) {
        return Product.find("from Product p where p.archived = false and p.price > 0 order by p.visitsCount desc").fetch(count);
    }

    public SiteMapPath getSitePath(ProductFilter filter) {
        SiteMapPath siteMapPath = new SiteMapPath();
        List<SitePath> sitePaths = new ArrayList<SitePath>();
        Category category = Category.findById(filter.categoryId);
        siteMapPath.currentCategory = category;
        List<Category> categories = Category
                .find("leftValue < ? and rightValue > ?",
                        category.leftValue, category.rightValue).fetch();
        for (Category cat : categories) {
            SitePath sitePath = new SitePath();
            sitePath.title = cat.title;
            sitePath.filter.categoryId = cat.id;
            sitePaths.add(sitePath);
        }
        SitePath sitePath1 = new SitePath();
        sitePath1.title = category.title;
        sitePath1.filter.categoryId = category.id;
        sitePaths.add(sitePath1);
        if (filter.brandId > 0) {
            if (filter.productId == 0) {
                sitePaths.remove(sitePaths.size() - 1);
            }
            Brand brand = Brand.findById(filter.brandId);
            if (brand == null)
                throw new NotFound("Такого производителя не существует");
            SitePath sitePath = new SitePath();
            sitePath.title = brand.title;
            sitePath.filter.brandId = brand.id;
            sitePath.filter.categoryId = filter.categoryId;
            sitePaths.add(sitePath);
        }

        if (filter.pageNumber > 1) {
            SitePath sitePath = new SitePath();
            //TODO: localize this
            sitePath.title = "Лист " + String.valueOf(filter.pageNumber);

            sitePath.filter.brandId = filter.brandId;
            sitePath.filter.categoryId = filter.categoryId;
            sitePath.filter.pageNumber = filter.pageNumber;
            sitePaths.add(sitePath);
        }

        if (filter.productId == 0) {
            sitePaths.remove(sitePaths.size() - 1);
        } else {
            if (filter.showProduct) {
                Product product = getById(filter.productId);
                SitePath sitePath = new SitePath();
                sitePath.title = product.title;
                sitePath.filter.brandId = filter.brandId;
                sitePath.filter.categoryId = filter.categoryId;
                sitePath.filter.pageNumber = filter.pageNumber;
                sitePath.filter.productId = product.id;
                sitePaths.add(sitePath);
            }
        }
        siteMapPath.sitePaths = sitePaths;

        return siteMapPath;

    }

    public PaginatedList<Product> list(ProductFilter filter) {
        if (filter.categoryId > 0) {
            Category category = Category.findById(filter.categoryId);
            List<Object> searchParams = new ArrayList<Object>();
            searchParams.add(category.leftValue);
            searchParams.add(category.rightValue);
            String additionalQueryPart = "";
            String query = "Select " +
                    "new Product(p.id, " +
                    "   p.title, " +
                    "   p.price, " +
                    "   p.shortDescription, " +
                    "   p.images.size) " +
                    "from Product p, Category c " +
                    "where p.category.id = c.id and " +
                    "c.leftValue >= ? and c.rightValue <= ? and (archived = 0 or archived is null)";
            if (filter.brandId > 0) {
                additionalQueryPart = String.format("and p.brand.id = ?");
                searchParams.add(filter.brandId);
            }
            query += additionalQueryPart;
            List<Product> list = Product.find(query, searchParams.toArray()).fetch(filter.pageNumber, filter.pageSize);
            PaginatedList<Product> paginatedList = new PaginatedList<Product>(list);
            String totalCountQuery = "Select " +
                    "   count(p.id) from Product p, " +
                    "   Category c " +
                    "where p.category.id = c.id and " +
                    "c.leftValue >= ? and c.rightValue <= ? and (archived = 0 or archived is null)";
            totalCountQuery += additionalQueryPart;
            paginatedList.totalCount = Product.count(totalCountQuery, searchParams.toArray());
            paginatedList.pageSize = filter.pageSize;
            paginatedList.pageNumber = filter.pageNumber;
            return paginatedList;
        }
        throw new NotImplementedException();
    }

    public PaginatedList<Product> search(String query, int pageNumber, int pageSize, int categoryId, int brandId) {
        pageSize = pageSize == 0 ? 10 : pageSize;
        List<Product> products = Product.find("select new Product(p.id," +
                " p.title," +
                " p.price," +
                " p.shortDescription," +
                " p.images.size)" +
                " from Product p where p.title like ? and archived = false", String.format("%%%1$s%%", query)).fetch(pageNumber, pageSize);
        long totalCount = Product.count("from Product p where p.title like ?  and archived = false", String.format("%%%1$s%%", query));
        PaginatedList<Product> result = new PaginatedList<Product>(products, (int) totalCount, pageSize);
        return result;
    }

    public void archive(int productId) {
        Product p = Product.findById(productId);
        p.archived = true;
        p.save();
    }

    public void restore(int productId) {
        Product p = Product.findById(productId);
        p.archived = false;
        p.save();
    }

    public void update(Product product) {
        Product fromDB = Product.findById(product.id);
        fromDB.title = product.title;
        fromDB.shortDescription = product.shortDescription;
        fromDB.description = product.description;
        fromDB.price = product.price;
        fromDB.save();
    }

    public Product getForEdit(int productId) {
        return Product.findById(productId);
    }

    public void deleteImage(int productId, int imageId) {
        ProductImageRecord.delete("productId = ? and imageId = ?", productId, imageId);
        Image.delete("id = ?", imageId);
    }

    public void addImage(int productId, Image image) {
        Product product = Product.findById(productId);
        image.save();
        product.images.add(image);
        product.save();
    }

    public void save(Product product, Image image) {
        addBrand(product);
        addImage(product, image);
        addCategory(product);
        save(product);
    }

    private void addCategory(Product product) {
        if (product.categoryId > 0) {
            Category category = Category.findById(product.categoryId);
            if (category != null) {
                product.category = category;
            }
        }
    }

    private void addImage(Product product, Image image) {
        if (image == null)
            return;
        Image fromDB = Image.find("byMd5", image.md5).first();
        if (fromDB != null) {
            product.images = new ArrayList<Image>();
            product.images.add(fromDB);
        } else {
            image.save();
            product.images = new ArrayList<Image>();
            product.images.add(image);
        }
    }

    private void addBrand(Product product) {
        if (product.brandName != null && !product.brandName.equalsIgnoreCase("")) {
            Brand brand = Brand.find("byTitle", product.brandName).first();
            if (brand == null) {
                brand = new Brand();
                brand.title = product.brandName;
                brand.save();
            }
            product.brand = brand;
        }
    }

    private List<Product> getListWithCriteriaBuilder(ProductFilter filter) {
        CriteriaBuilder builder = Product.em().getCriteriaBuilder();
        Category category = Category.findById(filter.categoryId);
        CriteriaQuery<Product> criteria = builder.createQuery(Product.class);
        Root<Product> productRoot = criteria.from(Product.class);
        criteria.select(productRoot);
        Path<Integer> leftValue = productRoot.get("category").get("leftValue");
        Predicate ge = builder.ge(leftValue, category.leftValue);
        Path<Integer> rightValue = productRoot.get("category").get("rightValue");
        Predicate le = builder.le(rightValue, category.rightValue);
        criteria.where(ge, le);

        TypedQuery<Product> query = Product.em().createQuery(criteria);
        query.setMaxResults(filter.pageSize);
        query.setFirstResult(filter.pageNumber);
        List<Product> products = query.getResultList();
        return products;
    }
}
